/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

#define KEYS_SZ 20

volatile unsigned char k;

int init_keyb(struct node_fs *this);
int deinit_keyb(struct node_fs *this);

unsigned char keys[KEYS_SZ];
int kptr = 0;

void put_key(unsigned char k)
{
	keys[kptr++] = k;
	if(kptr >= KEYS_SZ)
		kptr = 0;
}

unsigned char get_key()
{
	unsigned char ret_key;
	int i;
	ret_key = keys[0];
	for(i=0; i<kptr; i++) {
		keys[i] = keys[i+1];
	}
	kptr--;
	return ret_key;
}

void clear_keys()
{
	kptr = 0;
	inb(0x60);
	inb(0x60);
	inb(0x60);
	inb(0x60);
}

static int read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	int err;
	int i;
	err = kptr > 0 ? 0 : ENODATA;
	if(!err) {
		for(i=0; i<count; i++) {
			*((unsigned char *)buf + i) = get_key();
		}
	}
	return err;
}

struct node_fs keyb_console = {
	.type = TYPE_FS_SERVER,
	.name = "keyboard",
	.init = init_keyb,
	.deinit = deinit_keyb,
	.read = read,
	.write = dummy,
	.enumerate = dummy,
	.notify = dummy,
	.class = dummy,
	NODE_FS_INIT_VARS,
	NODE_FS_LIST_INIT,
};

#define KEY_F1 0x80
#define KEY_F2 0x81
#define KEY_F3 0x82
#define KEY_F4 0x83
#define KEY_F5 0x84
#define KEY_F6 0x85
#define KEY_F7 0x86
#define KEY_F8 0x87
#define KEY_F9 0x88
#define KEY_F10 0x89
#define KEY_F11 0x8a
#define KEY_F12 0x8b
#define KEY_UP 0x8c
#define KEY_LEFT 0x8d
#define KEY_DOWN 0x8e
#define KEY_RIGHT 0x8f
#define KEY_HOME 0x90
#define KEY_PGUP 0x91
#define KEY_PGDOWN 0x92
#define KEY_END 0x93
#define KEY_INS 0x94
#define KEY_DEL 0x95

//basic keymap
static int keymap[] =
{
	0,0x1B,'1','2','3','4','5','6',
	'7','8','9','0','-','=','\b','\t',
	'q','w','e','r','t','y','u','i',
	'o','p','[',']','\n',0,'a','s',
	'd','f','g','h','j','k','l',';',
	'\'','`',0,'\\','z','x','c','v',
	'b','n','m',',','.','/',0,0,
	0,' ',0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LEFT,'5',KEY_RIGHT,'+',KEY_END,
	KEY_DOWN,KEY_PGDOWN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};

static int shift_keymap[] =
{
	0,0x1B,'!','@','#','$','%','^',
	'&','*','(',')','_','+','\b','\t',
	'Q','W','E','R','T','Y','U','I',
	'O','P','{','}','\n',0,'A','S',
	'D','F','G','H','J','K','L',':',
	'\"','~',0,'|','Z','X','C','V',
	'B','N','M','<','>','?',0,0,
	0,' ',0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LEFT,'5',KEY_RIGHT,'+',KEY_END,
	KEY_DOWN,KEY_PGDOWN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};



static struct event_info keyb_event_info = {.node = &keyb_console, .buf = keys, .offset = 0, .count = 0
};

static struct node_fs keyb_event_fs = {
	.type = TYPE_FS,
	.name = "keyboard",
	NODE_FS_INIT_INIT,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
	.class = &keyb_event_info
};

struct node_fs *event_fs;


char shift_status = 0;

/*This plugs in an event to the /event directory event if that is removed. This is done to notify the event manager*/
int keyb_irq()
{
	unsigned char x;
	x = inb(0x60);
	if(x == 0x2a || x == 0x36) {
		shift_status = 1;
	} else if(x == 0xaa || x == 0xb6) {
		shift_status = 0;
	} else {
		if(!(x & 0x80)) {
			if(!shift_status) {
				put_key(keymap[x]);
			} else {
				put_key(shift_keymap[x]);
			}
			keyb_event_info.count = kptr;
			fs_noparse_link(event_fs, &keyb_event_fs);
		}
	}
	return 0;
}

int reboot(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	printk("Rebooting system...\n");
	clear_keys();
	outb(0x64, 0xfe);
}

struct node_fs reboot_fs = {
	.name = "reboot",
	.type = TYPE_FS_CLIENT,
	.read = dummy,
	.write = reboot,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_INIT_VARS,
	NODE_FS_INIT_INIT,
	NODE_FS_LIST_INIT,
};

int init_keyb(struct node_fs *this)
{
	arch_configure_interrupt_handler(1, keyb_irq);
	fs_link("/event", &keyb_event_fs); //Plug in an event generator
	event_fs = fs_get("/event");
	fs_link("/class/power", &reboot_fs);
	clear_keys();
	return 0;
}

int deinit_keyb(struct node_fs *this)
{
	arch_remove_interrupt_handler(1);
	return 0;
}

int i8042_read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	return 0;
}

int i8042_write(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	switch(offset) {
		case DRIVER_START:
			fs_link("/class/console", &keyb_console);
			keyb_console.init(&keyb_console);
			return 0;
		case DRIVER_STOP:
			keyb_console.deinit(&keyb_console);
			return 0;
		default:
			return ENOSUP;
	}
	return 0;
}

struct node_fs i8042_driver = {
	.name = "i8042_driver",
	.type = TYPE_FS_BOTH,
	NODE_FS_INIT_VARS,
	.class = NULL,
	.read = i8042_read,
	.write = i8042_write,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_INIT_INIT,
	NODE_FS_LIST_INIT,
};

int register_keyb()
{
	fs_link("/driver", &i8042_driver);
	return 0;
}
